package github.sf.fw.tools.db.ddldiff;

import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;

/**
 * @author zhoup
 * @since 2023/7/20
 */
public class ColumnDefinitionDiff {
    private final List<SQLColumnDefinition> oldColumnDefinitions;
    private final List<SQLColumnDefinition> newColumnDefinitions;

    public ColumnDefinitionDiff(List<SQLColumnDefinition> oldColumnDefinitions, List<SQLColumnDefinition> newColumnDefinitions) {
        this.oldColumnDefinitions = oldColumnDefinitions;
        this.newColumnDefinitions = newColumnDefinitions;
    }

    /**
     * 对比两sql建表语句的差异
     * 大致思路：
     * 先移除掉首尾相同的元素，然后用两个数组剩下的元素使用动态规划构建最长子序列的二维矩阵
     * 遍历新数据剩下的元素，然后看新数据中的元素是否在老数据中出现，若未出现，则表示为新增的
     * 如出现，则借助最长子序列的二维矩阵来判断怎么移动
     * 最后，看看老数组中有哪些元素没和新数组中的元素匹配上，表示为删除的元素
     */
    public DDLUpdater.SQLColumnDefinitionAnalysisContext diff() {
        DDLUpdater.SQLColumnDefinitionAnalysisContext analysisContext = new DDLUpdater.SQLColumnDefinitionAnalysisContext();
        List<DDLUpdater.SQLColumnDefinitionUpdate> updateList = new ArrayList<>();
        Pair<Integer, Integer> oldAndNewPre = indexOfDiffElementInPre();
        if (Objects.equals(oldAndNewPre.getLeft(), oldColumnDefinitions.size())
            && Objects.equals(oldAndNewPre.getRight(), newColumnDefinitions.size())) {
            //不需要更新
            analysisContext.setUseNewDDL(false);
            analysisContext.setSqlColumnDefinitionUpdateStatuses(updateList);
            return analysisContext;
        }
        Pair<Integer, Integer> oldAndNewSuf = indexOfDiffElementInSuf();
        List<SQLColumnDefinition> oldDiffColumnDefinitions = oldColumnDefinitions.subList(oldAndNewPre.getLeft(), oldAndNewSuf.getLeft() + 1);
        List<SQLColumnDefinition> newDiffColumnDefinitions = newColumnDefinitions.subList(oldAndNewPre.getRight(), oldAndNewSuf.getRight() + 1);
        int[][] dp = sameSubListDp(oldDiffColumnDefinitions, newDiffColumnDefinitions);
        Map<String, SQLColumnDefinition> oldColumnDefinitionMapByName = createColumnDefinitionMapByName(oldDiffColumnDefinitions);
        int beforeIdx = Math.max(oldAndNewPre.getLeft() - 1, -1);
        SQLColumnDefinition before;
        if (beforeIdx >= 0) {
            before = oldColumnDefinitions.get(beforeIdx);
        } else {
            before = new SQLColumnDefinition();
            SQLName sqlName = new SQLIdentifierExpr("first");
            before.setName(sqlName);
        }
        for (int n = 0; n < newDiffColumnDefinitions.size(); n++) {
            SQLColumnDefinition newDiffColumnDefinition = newDiffColumnDefinitions.get(n);
            String columnName = DDLUpdater.pureColumnName(newDiffColumnDefinition.getColumnName());
            if (oldColumnDefinitionMapByName.containsKey(columnName)) {
                SQLColumnDefinition oldDefinition = oldColumnDefinitionMapByName.get(columnName);
                if (DDLUpdater.columnEqual(newDiffColumnDefinition, oldDefinition)) {
                    //改变顺序
                    moveColumnDefinition(updateList, before, newDiffColumnDefinition);
                    int o = oldDiffColumnDefinitions.indexOf(oldDefinition);
                    //优化：利用最长子串匹配算法，最小化修改sql，
                    boolean same = false;
                    while (n < newDiffColumnDefinitions.size() && o < oldDiffColumnDefinitions.size()) {
                        SQLColumnDefinition sqlColumnDefinition = newDiffColumnDefinitions.get(n);
                        if (dp[o][n] != 0) {
                            same = true;
                            n++;
                            o++;
                            oldColumnDefinitionMapByName.remove(DDLUpdater.pureColumnName(sqlColumnDefinition.getColumnName()));
                        } else {
                            n--;
                            same = false;
                            break;
                        }
                    }
                    if ((n == newDiffColumnDefinitions.size() || o == oldDiffColumnDefinitions.size()) && same) {
                        //表示已经有一个遍历到最后一个元素了,且是+1后退出的循环，此时需要-1
                        n--;
                    }
                } else {
                    //改变顺序和类型
                    modifyAndMoveColumnDefinition(updateList, before, newDiffColumnDefinition);
                }
                //移除
                oldColumnDefinitionMapByName.remove(columnName);
            } else {
                //添加列
                addColumnDefinition(updateList, before, newDiffColumnDefinition);
            }
            before = newDiffColumnDefinitions.get(n);
        }
        if (!oldColumnDefinitionMapByName.isEmpty()) {
            for (SQLColumnDefinition columnDefinition : oldColumnDefinitionMapByName.values()) {
                //需要删除的列
                dropColumnDefinition(updateList, columnDefinition);
            }
        }
        analysisContext.setSqlColumnDefinitionUpdateStatuses(updateList);
        return analysisContext;
    }


    void moveColumnDefinition(List<DDLUpdater.SQLColumnDefinitionUpdate> updateList, SQLColumnDefinition before, SQLColumnDefinition current) {
        Objects.requireNonNull(before, "move类似before不能为空");
        DDLUpdater.SQLColumnDefinitionUpdate definitionUpdate = new DDLUpdater.SQLColumnDefinitionUpdate(current, DDLUpdater.ColumnDefinitionUpdateStatus.MOVE);
        definitionUpdate.setInsertType(DDLUpdater.InsertType.after);
        definitionUpdate.setLocationColumnDefinition(before);
        updateList.add(definitionUpdate);

    }

    void modifyAndMoveColumnDefinition(List<DDLUpdater.SQLColumnDefinitionUpdate> updateList, SQLColumnDefinition before, SQLColumnDefinition current) {
        DDLUpdater.SQLColumnDefinitionUpdate definitionUpdate = new DDLUpdater.SQLColumnDefinitionUpdate(current, DDLUpdater.ColumnDefinitionUpdateStatus.MODIFY_AND_MOVE);
        if (before != null) {
            definitionUpdate.setInsertType(DDLUpdater.InsertType.after);
            definitionUpdate.setLocationColumnDefinition(before);
        }
        updateList.add(definitionUpdate);
    }

    void dropColumnDefinition(List<DDLUpdater.SQLColumnDefinitionUpdate> updateList, SQLColumnDefinition deleteColumnDefinition) {
        DDLUpdater.SQLColumnDefinitionUpdate definitionUpdate = new DDLUpdater.SQLColumnDefinitionUpdate(deleteColumnDefinition, DDLUpdater.ColumnDefinitionUpdateStatus.DROP);
        updateList.add(definitionUpdate);
    }

    void addColumnDefinition(List<DDLUpdater.SQLColumnDefinitionUpdate> updateList, SQLColumnDefinition before, SQLColumnDefinition addDefinition) {
        DDLUpdater.SQLColumnDefinitionUpdate definitionUpdate = new DDLUpdater.SQLColumnDefinitionUpdate(addDefinition, DDLUpdater.ColumnDefinitionUpdateStatus.ADD);
        if (before != null) {
            definitionUpdate.setInsertType(DDLUpdater.InsertType.after);
            definitionUpdate.setLocationColumnDefinition(before);
        }
        updateList.add(definitionUpdate);

    }

    private Map<String, SQLColumnDefinition> createColumnDefinitionMapByName(List<SQLColumnDefinition> oldDiffColumnDefinitions) {
        Map<String, SQLColumnDefinition> columnDefinitionMap = new LinkedHashMap<>();
        oldDiffColumnDefinitions.forEach(definition -> {
            columnDefinitionMap.put(DDLUpdater.pureColumnName(definition.getColumnName()), definition);
        });
        return columnDefinitionMap;
    }

    private Pair<Integer, Integer> indexOfDiffElementInPre() {
        int oldSize = oldColumnDefinitions.size();
        int oldStart = 0;
        int newSize = newColumnDefinitions.size();
        int newStart = 0;
        while (oldStart < oldSize && newStart < newSize) {
            SQLColumnDefinition oldStartDefinition = oldColumnDefinitions.get(oldStart);
            SQLColumnDefinition newStartDefinition = newColumnDefinitions.get(newStart);
            if (!DDLUpdater.columnEqual(oldStartDefinition, newStartDefinition)) {
                return Pair.of(oldStart, newStart);
            }
            oldStart++;
            newStart++;
        }
        return Pair.of(oldStart, newStart);
    }

    private Pair<Integer, Integer> indexOfDiffElementInSuf() {
        int oldSize = oldColumnDefinitions.size();
        int newSize = newColumnDefinitions.size();
        while (oldSize > 1 && newSize > 1) {
            SQLColumnDefinition oldStartDefinition = oldColumnDefinitions.get(oldSize - 1);
            SQLColumnDefinition newStartDefinition = newColumnDefinitions.get(newSize - 1);
            if (!DDLUpdater.columnEqual(oldStartDefinition, newStartDefinition)) {
                return Pair.of(oldSize - 1, newSize - 1);
            }
            oldSize--;
            newSize--;
        }
        return Pair.of(oldSize - 1, newSize - 1);
    }

    /**
     * 获取两个SQLColumnDefinition的最大相同子串
     */
    private int[][] sameSubListDp(List<SQLColumnDefinition> oldDefs, List<SQLColumnDefinition> newDefs) {
        int[][] dp = new int[oldDefs.size() + 1][newDefs.size() + 1];
        for (int i = 0; i < dp.length; i++) {
            dp[i][0] = 0;
        }
        Arrays.fill(dp[0], 0);
        for (int o = 1; o < dp.length; o++) {
            for (int n = 1; n < dp[0].length; n++) {
                if (DDLUpdater.columnEqual(oldDefs.get(o - 1), newDefs.get(n - 1))) {
                    dp[o][n] = dp[o - 1][n - 1] + 1;
                }
            }
        }
        int[][] ndp = new int[oldDefs.size()][newDefs.size()];
        for (int o = 1; o < dp.length; o++) {
            if (dp[0].length - 1 >= 0) System.arraycopy(dp[o], 1, ndp[o - 1], 0, dp[0].length - 1);
        }
        return ndp;
    }

}
